package com.yuqih.common.dao.impl;

import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import javax.persistence.GeneratedValue;
import javax.persistence.Id;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.Generated;
import org.hibernate.annotations.GenerationTime;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;

import com.yuqih.common.config.IFrameworkConfig;
import com.yuqih.common.dao.ICommonDao;
import com.yuqih.common.dao.IConditionHandler;
import com.yuqih.common.dao.ISortParser;
import com.yuqih.common.dto.FieldDescriptor;
import com.yuqih.common.dto.IFieldSpecified;
import com.yuqih.common.dto.PageMetadata;
import com.yuqih.common.dto.TypeDescriptor;
import com.yuqih.common.exception.CodedException;
import com.yuqih.common.service.IIdGenerator;

public class CommonDao implements ICommonDao {

	private final ConversionService conversionService = new DefaultConversionService();

	private SqlSessionTemplate sqlSessionTemplate;
	private IFrameworkConfig frameworkConfig;
	private Map<String, IIdGenerator> idGeneratorMapping;

	private String getStatement(Class<?> type, String action) {
		String namespace = frameworkConfig.getMybatisNamespace(type);
		return namespace + "." + type.getSimpleName() + "." + action;
	}

	@Override
	public <T> int insert(Class<T> type, T entity) {
		TypeDescriptor entityDescriptor = frameworkConfig.getTypeDescriptor(type);
		Collection<FieldDescriptor> fieldDescriptors = entityDescriptor.getFieldDescriptors();
		Date now = new Date();
		beforeInsert(entity, fieldDescriptors, now);
		return sqlSessionTemplate.insert(getStatement(type, "insert"), entity);
	}
	
	private <T> void beforeInsert(T entity, Collection<FieldDescriptor> fieldDescriptors, Date now) {
		try {
			for (FieldDescriptor fieldDescriptor : fieldDescriptors) {

				Id id = fieldDescriptor.getAnnotation(Id.class);
				if (id != null) {
					GeneratedValue generatedValue = fieldDescriptor.getAnnotation(GeneratedValue.class);
					if (generatedValue != null) {
						Object value = PropertyUtils.getProperty(entity, fieldDescriptor.getFieldName());
						if (Objects.equals(fieldDescriptor.getFieldType(), String.class) && value != null
								&& StringUtils.isBlank((String) value)) {
							value = null;
						}

						if (value == null) {
							IIdGenerator idGenerator = idGeneratorMapping.get(generatedValue.generator());
							value = idGenerator.generate();
							PropertyUtils.setProperty(entity, fieldDescriptor.getFieldName(), value);
						}

					}
				}

				Generated generated = fieldDescriptor.getAnnotation(Generated.class);
				if (generated != null) {
					Object value = PropertyUtils.getProperty(entity, fieldDescriptor.getFieldName());
					if (value == null) {
						if (Objects.equals(generated.value(), GenerationTime.INSERT)
								|| Objects.equals(generated.value(), GenerationTime.ALWAYS)) {
							PropertyUtils.setProperty(entity, fieldDescriptor.getFieldName(), now);
						}
					}
				}

				ColumnDefault columnDefault = fieldDescriptor.getAnnotation(ColumnDefault.class);
				if (columnDefault != null) {
					Object value = PropertyUtils.getProperty(entity, fieldDescriptor.getFieldName());
					if (value == null) {
						value = conversionService.convert(columnDefault.value(), fieldDescriptor.getFieldType());
						PropertyUtils.setProperty(entity, fieldDescriptor.getFieldName(), value);
					}
				}

			}
		} catch (Exception e) {
			throw CodedException.from(e);
		}
	}

	@Override
	public <T> int insertBatch(Class<T> type, List<T> entities) {
		TypeDescriptor entityDescriptor = frameworkConfig.getTypeDescriptor(type);
		Collection<FieldDescriptor> fieldDescriptors = entityDescriptor.getFieldDescriptors();
		Date now = new Date();
		for (T entity : entities) {
			beforeInsert(entity, fieldDescriptors, now);
		}
		Map<String, Object> parameter = new HashMap<String, Object>();
		parameter.put("entities", entities);
		return sqlSessionTemplate.insert(getStatement(type, "insertBatch"),  parameter);
	}

	@Override
	public <T> int update(Class<T> type, T entity) {
		TypeDescriptor entityDescriptor = frameworkConfig.getTypeDescriptor(type);
		Collection<FieldDescriptor> fieldDescriptors = entityDescriptor.getFieldDescriptors();
		Date now = new Date();
		beforeUpdate(entity, fieldDescriptors, now);
		return sqlSessionTemplate.update(getStatement(type, "update"), entity);
	}
	
	private <T> void beforeUpdate(T entity, Collection<FieldDescriptor> fieldDescriptors, Date now) {
		try {
			for (FieldDescriptor fieldDescriptor : fieldDescriptors) {

				Id id = fieldDescriptor.getAnnotation(Id.class);
				if (id != null) {

					Object value = PropertyUtils.getProperty(entity, fieldDescriptor.getFieldName());
					if (Objects.equals(fieldDescriptor.getFieldType(), String.class) && value != null
							&& StringUtils.isBlank((String) value)) {
						value = null;
					}

					if (value == null) {
						throw new IllegalArgumentException("修改时id传入空值");
					}

				}

				Generated generated = fieldDescriptor.getAnnotation(Generated.class);
				if (generated != null) {
					Object value = PropertyUtils.getProperty(entity, fieldDescriptor.getFieldName());
					if (value == null) {
						if (Objects.equals(generated.value(), GenerationTime.ALWAYS)) {
							PropertyUtils.setProperty(entity, fieldDescriptor.getFieldName(), now);
						}
					}
				}

			}
		} catch (Exception e) {
			throw CodedException.from(e);
		}
	}

	@Override
	public <T> int updateBatch(Class<T> type, List<T> entities) {
		TypeDescriptor entityDescriptor = frameworkConfig.getTypeDescriptor(type);
		Collection<FieldDescriptor> fieldDescriptors = entityDescriptor.getFieldDescriptors();
		Date now = new Date();
		for (T entity : entities) {
			beforeUpdate(entity, fieldDescriptors, now);
		}
		Map<String, Object> parameter = new HashMap<String, Object>();
		parameter.put("entities", entities);
		return sqlSessionTemplate.update(getStatement(type, "updateBatch"), parameter);
	}

	@Override
	public <T> int delete(Class<T> type, Object id) {
		return sqlSessionTemplate.delete(getStatement(type, "delete"), id);
	}

	@Override
	public <T> int deleteBatch(Class<T> type, List<?> ids) {
		Map<String, Object> parameter = new HashMap<String, Object>();
		parameter.put("ids", ids);
		return sqlSessionTemplate.delete(getStatement(type, "deleteBatch"), parameter);
	}

	@Override
	public <T> T selectById(Class<T> type, Object id) {
		return sqlSessionTemplate.selectOne(getStatement(type, "selectById"), id);
	}

	@Override
	public <T> List<T> selectList(Class<T> type, Object condition, PageMetadata pageMetadata) {
		Map<String, Object> parameter = new HashMap<String, Object>();

		if (condition instanceof IFieldSpecified) {
			IFieldSpecified fieldSpecified = (IFieldSpecified) condition;
			if (fieldSpecified.getSettedFieldNames().isEmpty()) {
				condition = null;
			} else {
				IConditionHandler conditionHandler = frameworkConfig.getConditionHandler(type);
				if (conditionHandler != null) {
					conditionHandler.handle(parameter, condition);
				}
			}
		}
		parameter.put("condition", condition);

		if (pageMetadata != null) {
			ISortParser sortParser = frameworkConfig.getSortParser(type);
			if (sortParser != null) {
				pageMetadata.setSort(sortParser.parse(pageMetadata.getSort()));
			}
		}
		parameter.put("page", pageMetadata);

		return sqlSessionTemplate.selectList(getStatement(type, "selectList"), parameter);
	}

	@Override
	public <T> int count(Class<T> type, Object condition) {
		Map<String, Object> parameter = new HashMap<String, Object>();

		if (condition instanceof IFieldSpecified) {
			IFieldSpecified fieldSpecified = (IFieldSpecified) condition;
			if (fieldSpecified.getSettedFieldNames().isEmpty()) {
				condition = null;
			} else {
				IConditionHandler conditionHandler = frameworkConfig.getConditionHandler(type);
				if (conditionHandler != null) {
					conditionHandler.handle(parameter, condition);
				}
			}
		}
		parameter.put("condition", condition);
		return sqlSessionTemplate.selectOne(getStatement(type, "count"), parameter);
	}

	/**
	 * @param sqlSessionTemplate
	 *            the sqlSessionTemplate to set
	 */
	public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
		this.sqlSessionTemplate = sqlSessionTemplate;
	}

	/**
	 * @param frameworkConfig
	 *            the frameworkConfig to set
	 */
	public void setFrameworkConfig(IFrameworkConfig frameworkConfig) {
		this.frameworkConfig = frameworkConfig;
	}

	/**
	 * @param idGeneratorMapping
	 *            the idGeneratorMapping to set
	 */
	public void setIdGeneratorMapping(Map<String, IIdGenerator> idGeneratorMapping) {
		this.idGeneratorMapping = idGeneratorMapping;
	}

}
